/*完成日志器模块
    1、抽象日志器基类
    2、派生出不同的子类（同步日志器类&异步日志器类）
*/
#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include <atomic>
#include <mutex>
#include <cstdarg>
#include "looper.hpp"
#include "unordered_map"
namespace kaizi
{
    // 同步日志器，对多个落地方向进行数据的写入操作
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>; // 智能指针进行类对象的管理
        // 构造函数
        Logger(const std::string &logger_name,   // 日志器名称
               LogLevel::value level,            // 日志限制等级
               Formatter::ptr &formatter,        // 日志格式化对象
               std::vector<LogSink::ptr> &sinks) // 日志落地对象，我们将数组传递进来进行初始化操作
            : _logger_name(logger_name), _limit_level(level), _formatter(formatter), _sinks(sinks.begin(), sinks.end())
        {
        }
        // 返回日志器名称
        const std::string &name() // 避免未接对名称进行修改的操作
        {
            return _logger_name;
        }

        // 完成构造日志消息对象过程并进行格式化，得到格式化后的日志消息字符串，然后进行落地输出
        void debug(const std::string &file, size_t line, const std::string &fmt, ...) // 根据fmt取出后面的不定参数据，组成一个LogMsg对象，然后调用_log接口进行日志记录
        {
            // 按日志等级判断是否输出，将 fmt 和可变参数拼接成完整日志，最终调用 _log 落地

            // 通过传入的参数构造一个日志消息对象，进行日志的格式化操作，进行落地
            // 1、判断当前的日志是否到达了输出等级
            if (LogLevel::value::DEBUG < _limit_level) // 不能进行输出操作
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;                                 // 定义可变参数列表容器，用于存储、遍历可变参数。
            va_start(ap, fmt);                          // 获取指定参数的起始地址，这里就是获取fmt参数后第一个参数的起始地址,就是...
            char *res;                                  // 定义一个空间指针，后面会将格式化的结果存在这个指针中的
            int ret = vasprintf(&res, fmt.c_str(), ap); // 按 fmt 的格式（如 "%d %s" ），把 ap 里的可变参数拼接成完整 C 字符串，存入 res 指向的内存
            if (ret == -1)                              // 格式化组织失败了
            {
                std::cout << "vasprintf failed!\n";
                return;
            }
            va_end(ap);                                         // 释放 ap 相关资源，避免内存问题
            serialize(LogLevel::value::DEBUG, file, line, res); // 调用日志器基类的serialize接口进行日志的格式化操作，并进行日志的落地
            free(res);                                          // 释放res指针指向的内存空间
        }
        void info(const std::string &file, size_t line, const std::string &fmt, ...) // 根据fmt取出后面的不定参数据，组成一个LogMsg对象，然后调用_log接口进行日志记录
        {
            // 按日志等级判断是否输出，将 fmt 和可变参数拼接成完整日志，最终调用 _log 落地

            // 通过传入的参数构造一个日志消息对象，进行日志的格式化操作，进行落地
            // 1、判断当前的日志是否到达了输出等级
            if (LogLevel::value::INFO < _limit_level) // 不能进行输出操作
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;                                 // 定义可变参数列表容器，用于存储、遍历可变参数。
            va_start(ap, fmt);                          // 获取指定参数的起始地址，这里就是获取fmt参数后第一个参数的起始地址,就是...
            char *res;                                  // 定义一个空间指针，后面会将格式化的结果存在这个指针中的
            int ret = vasprintf(&res, fmt.c_str(), ap); // 按 fmt 的格式（如 "%d %s" ），把 ap 里的可变参数拼接成完整 C 字符串，存入 res 指向的内存
            if (ret == -1)                              // 格式化组织失败了
            {
                std::cout << "vasprintf failed!\n";
                return;
            }
            va_end(ap);                                        // 释放 ap 相关资源，避免内存问题
            serialize(LogLevel::value::INFO, file, line, res); // 调用日志器基类的serialize接口进行日志的格式化操作，并进行日志的落地
            free(res);                                         // 释放res指针指向的内存空间
        }
        void warn(const std::string &file, size_t line, const std::string &fmt, ...) // 根据fmt取出后面的不定参数据，组成一个LogMsg对象，然后调用_log接口进行日志记录
        {
            // 按日志等级判断是否输出，将 fmt 和可变参数拼接成完整日志，最终调用 _log 落地

            // 通过传入的参数构造一个日志消息对象，进行日志的格式化操作，进行落地
            // 1、判断当前的日志是否到达了输出等级
            if (LogLevel::value::WARN < _limit_level) // 不能进行输出操作
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;                                 // 定义可变参数列表容器，用于存储、遍历可变参数。
            va_start(ap, fmt);                          // 获取指定参数的起始地址，这里就是获取fmt参数后第一个参数的起始地址,就是...
            char *res;                                  // 定义一个空间指针，后面会将格式化的结果存在这个指针中的
            int ret = vasprintf(&res, fmt.c_str(), ap); // 按 fmt 的格式（如 "%d %s" ），把 ap 里的可变参数拼接成完整 C 字符串，存入 res 指向的内存
            if (ret == -1)                              // 格式化组织失败了
            {
                std::cout << "vasprintf failed!\n";
                return;
            }
            va_end(ap);                                        // 释放 ap 相关资源，避免内存问题
            serialize(LogLevel::value::WARN, file, line, res); // 调用日志器基类的serialize接口进行日志的格式化操作，并进行日志的落地
            free(res);                                         // 释放res指针指向的内存空间
        }
        void error(const std::string &file, size_t line, const std::string &fmt, ...) // 根据fmt取出后面的不定参数据，组成一个LogMsg对象，然后调用_log接口进行日志记录
        {
            // 按日志等级判断是否输出，将 fmt 和可变参数拼接成完整日志，最终调用 _log 落地

            // 通过传入的参数构造一个日志消息对象，进行日志的格式化操作，进行落地
            // 1、判断当前的日志是否到达了输出等级
            if (LogLevel::value::ERROR < _limit_level) // 不能进行输出操作
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;                                 // 定义可变参数列表容器，用于存储、遍历可变参数。
            va_start(ap, fmt);                          // 获取指定参数的起始地址，这里就是获取fmt参数后第一个参数的起始地址,就是...
            char *res;                                  // 定义一个空间指针，后面会将格式化的结果存在这个指针中的
            int ret = vasprintf(&res, fmt.c_str(), ap); // 按 fmt 的格式（如 "%d %s" ），把 ap 里的可变参数拼接成完整 C 字符串，存入 res 指向的内存
            if (ret == -1)                              // 格式化组织失败了
            {
                std::cout << "vasprintf failed!\n";
                return;
            }
            va_end(ap);                                         // 释放 ap 相关资源，避免内存问题
            serialize(LogLevel::value::ERROR, file, line, res); // 调用日志器基类的serialize接口进行日志的格式化操作，并进行日志的落地
            free(res);                                          // 释放res指针指向的内存空间
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...) // 根据fmt取出后面的不定参数据，组成一个LogMsg对象，然后调用_log接口进行日志记录
        {
            // 按日志等级判断是否输出，将 fmt 和可变参数拼接成完整日志，最终调用 _log 落地

            // 通过传入的参数构造一个日志消息对象，进行日志的格式化操作，进行落地
            // 1、判断当前的日志是否到达了输出等级
            if (LogLevel::value::FATAL < _limit_level) // 不能进行输出操作
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;                                 // 定义可变参数列表容器，用于存储、遍历可变参数。
            va_start(ap, fmt);                          // 获取指定参数的起始地址，这里就是获取fmt参数后第一个参数的起始地址,就是...
            char *res;                                  // 定义一个空间指针，后面会将格式化的结果存在这个指针中的
            int ret = vasprintf(&res, fmt.c_str(), ap); // 按 fmt 的格式（如 "%d %s" ），把 ap 里的可变参数拼接成完整 C 字符串，存入 res 指向的内存
            if (ret == -1)                              // 格式化组织失败了
            {
                std::cout << "vasprintf failed!\n";
                return;
            }
            va_end(ap);                                         // 释放 ap 相关资源，避免内存问题
            serialize(LogLevel::value::FATAL, file, line, res); // 调用日志器基类的serialize接口进行日志的格式化操作，并进行日志的落地
            free(res);                                          // 释放res指针指向的内存空间
        }

    protected:
        void serialize(LogLevel::value level, const std::string &file, size_t line, char *str) // 对下面的操作进行了一个封装操作，因为每种等级里面的这个代码都是相同的
        {
            // 3、构造LogMsg日志消息类对象
            LogMsg msg(level, line, file, _logger_name, str); // 构造一个日志消息对象，并进行日志的格式化操作
            // 4、通过格式化工具对LogMsg对象进行格式化,得到格式化后的日志字符串
            std::stringstream ss;        // 定义一个字符串流类对象
            _formatter->format(ss, msg); // 调用日志格式化对象进行格式化操作,将日志内容格式化后输出到ss这个内存流中
            // 5、日志落地
            log(ss.str().c_str(), ss.str().size()); // 调用日志器基类的log接口进行日志落地
        }
        // 抽象的接口完成实际的落地输出---不同的日志器会有不同的实际落地方式
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex;        // 日志器的互斥锁
        std::string _logger_name; // 日志器名称
        // 如果下面的等级进行加锁保护的话锁冲突比较多，因为这个日志等级调用的比较频繁
        std::atomic<LogLevel::value> _limit_level; // 日志等级,定义为原子类型的成员，就能保证在多线程里面的原子访问了
        Formatter::ptr _formatter;                 // 日志格式化对象
        std::vector<LogSink::ptr> _sinks;          // 日志落地对象数组,可能存在多个落地方向，所以使用vector+智能指针进行管理
    };

    // 同步日志器
    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string &logger_name,         // 日志器名称
                   LogLevel::value level,                  // 日志限制等级
                   Formatter::ptr &formatter,              // 日志格式化对象
                   std::vector<LogSink::ptr> &sinks)       // 日志落地对象，我们将数组传递进来进行初始化操作
            : Logger(logger_name, level, formatter, sinks) // 使用抽象基类进行一个实例化操作
        {
        }

    protected:
        // 同步日志器，是将日志直接通过落地模块句柄进行日志落地
        void log(const char *data, size_t len)
        {
            // 它通过 RAII 机制确保锁的正确获取和释放，避免死锁问题
            // 同时提供了比 std::lock_guard 更灵活的锁管理方式
            std::unique_lock<std::mutex> lock(_mutex); // 自动进行加锁，等lock释放了锁会自动进行解锁的
            if (_sinks.empty())
                return;               // 如果这个落地对象数组是空的，那么我们直接返回了
            for (auto &sink : _sinks) // 遍历日志落地对象数组
            {
                sink->log(data, len); // 逐个调用 sink 的 log 方法，把日志数据输出到不同目标
            }
        }
    };

    // 异步日志器
    class AsyncLogger : public Logger
    {
    public:
        AsyncLogger(const std::string &logger_name,         // 日志器名称
                    LogLevel::value level,                  // 日志限制等级
                    Formatter::ptr &formatter,              // 日志格式化对象
                    std::vector<LogSink::ptr> &sinks,       // 日志落地对象，我们将数组传递进来进行初始化操作
                    AsyncType _looper_type)                 // 处理模式是什么
            : Logger(logger_name, level, formatter, sinks), // 使用抽象基类进行一个实例化操作
                                                            // 创建一个异步工作器，传入了一个回调函数，第二个参数是实际的类型，就是安全模式和非安全模式
                                                            // 对reallog进行一个参数绑定，第一个是this参数，再预留一个参数
                                                            // 通过reallog函数生成一个新的函数，生成一个新的可调用对象，这个新的函数只需要传入一个参数就行了，就是一个缓冲区对象
                                                            // 就是说我们在调用calback的时候，函数内部已经绑定了一个参数进去了，这个参数就是我们的Asynclogger日志器对象，真正调用的时候calback只需要传入一个buffer就行了
                                                            // 原先的我们的reallog是需要传入两个参数的，还有一个this对象，因为这个是类内函数我们直接将Asynclogger绑定进去就行了，后面就只用传一个参数了
              _looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::realLog, this, std::placeholders::_1), _looper_type)) // 异步工作器初始化
        {
        }
        void log(const char *data, size_t len) // 将数据写入缓冲区
        {
            _looper->push(data, len); // 将数据写入缓冲区,这里我们是不需要考虑线程安全的问题，因为push的线程就是安全的
        }
        // 实际一个实际落地函数（将缓冲区中的数据 进行落地）
        void realLog(Buffer &buf) // 传入一个缓冲区对象
        {
            if (_sinks.empty())
                return;               // 如果这个落地对象数组是空的，那么我们直接返回了
            for (auto &sink : _sinks) // 遍历日志落地对象数组
            {
                // 获取可读取数据的起始位置，以及可读取数据的大小，去进行一个实际的落地操作
                sink->log(buf.begin(), buf.readAbleSize()); // 逐个调用 sink 的 log 方法，把日志数据输出到不同目标
                // 这个过程是不需要进行加锁操作的，因为异步工作线程本身就是串行化的工作过程，不需要进行加锁保护的，我们只要保护线程安全的落入到缓冲区里面去，线程内部就能保证串性化的写入数据
            }
        }

    private:
        AsyncLooper::ptr _looper; // 异步日志循环器,利用智能指针进行对象的管理操作
    };

    enum class LoggerType
    {
        LOGGER_SYNC, // 同步日志器
        LOGGER_ASYNC // 异步日志器
    };
    /*使用建造者模式来建造日志器，而不要让用户直接去构造日志器，简化用户的使用复杂度

    建造者模式中，针对我们具体的对象得有一个建造者抽象类
    然后根据我们具体的对象派生出我们具体的类
    然后有了建造者类，我们通过指挥者类来指导建造者进行对象的创建
    指挥建造完零部件之后然后再去建造我们的复杂对象

    因为我们这里的建造是对零件顺序没有要求的，最主要的问题是参数过多，每个参数都需要进行构造
    */
    // 1、抽象一个日志器建造者类（完成日志器对象所需零部件的构建&日志器的构建）
    //  1、设置日志器类型
    //  2、将不同类型日志器的创建放到同一个日志建造者类中完成
    class LoggerBuilder
    {
    public:
        LoggerBuilder()
            : _logger_type(LoggerType::LOGGER_SYNC) // 默认生成的事同步日志器
              ,
              _limit_level(LogLevel::value::DEBUG) // 默认日志等级为DEBUG
              ,
              _looper_type(AsyncType::ASYNC_SAFE) // 默认异步处理模式为安全模式
        {
        }
        void buildLoggerType(LoggerType type) // 根据这个日志器的类型进行构造
        {
            _logger_type = type;
        }
        void buildEnableUnSafeAsync() // 启动一个非安全模式的操作
        {
            // 设置为非安全状态
            _looper_type = AsyncType::ASYNC_UNSAFE;
        }
        void buildLoggerName(const std::string &name) // 日志器名称
        {
            _logger_name = name;
        }
        void buildLoggerLevel(LogLevel::value level) // 日志器名称
        {
            _limit_level = level;
        }
        // 构造一个格式化器，就是设置下我们日志器的输出规则
        void buildFormatter(const std::string &pattern) // 日志器名称
        {
            // 通过pattern来设置日志格式化对象
            _formatter = std::make_shared<Formatter>(pattern); // 构造了一个Formatter对象，通过智能指针进行管理
        }
        // 根据拓展性，让用户自己设定日志落地的方向,所以我们这里使用模版和不定参
        template <typename SinkType, typename... Args>
        void buildSink(Args &&...args) // 创建落地器
        {
            LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...); // 对参数包进行一个完美转发，将参数展开传入，创建一个落地器
            _sinks.push_back(psink);                                                         // 将这个落地器添加到日志器的数组中
        }
        virtual Logger::ptr build() = 0; // 日志器的构建接口,返回的是一个日志器对象

    protected:
        AsyncType _looper_type;           // 异步工作处理模式
        LoggerType _logger_type;          // 日志器类型
        std::string _logger_name;         // 日志器名称
        LogLevel::value _limit_level;     // 日志等级
        Formatter::ptr _formatter;        // 日志格式化对象
        std::vector<LogSink::ptr> _sinks; // 日志落地对象数组,可能存在多个落地方向，所以使用vector+智能指针进行管理
    };
    // 2、派生出具体的建造者类---局部日志器的建造者&全局的日志器建造者（后边添加了全局单例管理器之后，将日志器添加全局管理）
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        // 返回对应的日志器
        Logger::ptr build() override // 日志器的构建接口
        {
            assert(_logger_name.empty() == false); // 必须有日志器名称
            if (_formatter.get() == nullptr)       // 如果是空的话就代表没有日志器
            {
                _formatter = std::make_shared<Formatter>(); // 构造一个默认的日志格式化对象
            }
            if (_sinks.empty()) // 如果没有落地对象的话就添加一个默认的落地器
            {
                buildSink<StdoutSink>(); // 添加一个标准输出方向的落地器
            }
            // 到这里东西都有了，那么我们就得进行创建对象的操作了
            if (_logger_type == LoggerType::LOGGER_ASYNC) // 如果类型是异步日志器的话，我们就返回一个异步日志器的对象
            {
                return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type); // 构造一个异步日志器对象并返回
            }
            // 走到这里就是同步日志器了
            return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks); // 构造一个同步日志器对象并返回
        }
    };

    class LoggerManager // 日志器管理器
    {
    public:
        // 我们这里使用的是懒汉单例模式，对象用的时候再创建，不用就不创建
        static LoggerManager &getInstance() // 获取单例对象
        {
            static LoggerManager eton; // 定义一个对象，静态局部变量
            // 在C++11之后，针对静态局部变量，编译器再编译的层面实现了线程安全，保证了单例对象的线程安全
            // 当静态局部变量没有构造完成之前，其他的线程进入就会阻塞，因为是静态变量，所以所有的线程获取到的都是同一个对象
            return eton; // 返回单例对象
        }
        void addLogger(Logger::ptr &logger) // 添加日志器
        {
            if(hasLogger(logger->name()))return ;//如果已经存在这个日志器的话，那么我们就不需要重进行添加了

            //日志器不存在的话我们就走进来记性日志器的添加操作了
            std::unique_lock<std::mutex> lock(_mutex);               // 对互斥锁进行一个管理操作
            _loggers.insert(std::make_pair(logger->name(), logger)); // 将日志器添加到日志器数组中
        }
        bool hasLogger(const std::string &name) // 判断是否存在某个日志器
        {
            std::unique_lock<std::mutex> lock(_mutex); // 对互斥锁进行一个管理操作
            auto it=_loggers.find(name); // 利用find函数进行查找
            if(it!=_loggers.end())
            {
                return true;//说明没有找到
            }
            return false;//找到了
        }
        Logger::ptr getLogger(const std::string &name) // 通过日志器名称获取日志器
        {
            std::unique_lock<std::mutex> lock(_mutex); // 对互斥锁进行一个管理操作
            auto it=_loggers.find(name); // 利用find函数进行查找
            if(it==_loggers.end())
            {
                return Logger::ptr();//说明没有找到,我们返回一个空的智能指针
                
            }
            return it->second;//找到了,就是日志器对象
        }
        Logger::ptr rootLogger()                      // 获取默认日志器
        {
            return _root_logger; // 返回默认日志器
        }
    private:
        LoggerManager() // 构造函数私有化，不允许外部创建对象
        {
            //这里不能是globalLoggerBuilder，不然是会卡主的
            std::unique_ptr<kaizi::LoggerBuilder> builder(new kaizi::LocalLoggerBuilder());//创建一个日志器构造器对象，让构造器指针进行智能管理
            builder->buildLoggerName("root"); // 设置日志器名称,其他的信息我们都会默认进行设置操作
            _root_logger=builder->build(); // 构造默认的日志器对象

            //默认日志器就是参数只用传个名字就行了，其他的就系统默认就行了，我们不需要设置，让建造者帮我们进行设置就行了
            _loggers.insert(std::make_pair(_root_logger->name(), _root_logger)); // 将默认日志器添加到日志器数组中
        } 
    private:
        std::mutex _mutex;        // 日志器管理器的互斥锁
        Logger::ptr _root_logger; // 默认日志器
        // 日志器名称和日志器对象之间的映射关系
        std::unordered_map<std::string, Logger::ptr> _loggers; // 日志器的数组
    };

    //设计一个全局日志器的建造者---在局部的基础上增加一个功能：将日志器添加到单例对象中
    class GlobalLoggerBuilder : public LoggerBuilder
    {
    public:
        // 返回对应的日志器
        Logger::ptr build() override // 日志器的构建接口
        {
            assert(_logger_name.empty() == false); // 必须有日志器名称
            if (_formatter.get() == nullptr)       // 如果是空的话就代表没有日志器
            {
                _formatter = std::make_shared<Formatter>(); // 构造一个默认的日志格式化对象
            }
            if (_sinks.empty()) // 如果没有落地对象的话就添加一个默认的落地器
            {
                buildSink<StdoutSink>(); // 添加一个标准输出方向的落地器
            }
            Logger::ptr logger ;//创建一个logger对象


            // 到这里东西都有了，那么我们就得进行创建对象的操作了
            if (_logger_type == LoggerType::LOGGER_ASYNC) // 如果类型是异步日志器的话，我们就返回一个异步日志器的对象
            {
                logger= std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type); // 构造一个异步日志器对象并返回
            }
            else
            {
                logger=std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
            }
            LoggerManager::getInstance().addLogger(logger); // 将日志器添加到日志器管理器中
            return  logger;
        }
        //外界就不用自己去添加了，实例化一个global的建造者对象，然后调用build方法就行了，我们这里就不用实现了，因为我们这里的全局日志器的建造者和局部日志器的建造者是一样的，只是多了一个功能，将日志器添加到单例对象中
    };

}
#endif
